﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Data.Schema.ScriptDom.Sql;
using System.Globalization;

namespace SQLite2CELib
{
    class Utils
    {
        private TableSchemaHelper _schemaHelper;
        private Dictionary<String, String> _entityNameAndLeftJoinedEntityName;
        private List<String> _tablesInQuery;
        private Dictionary<String, String> _tableAliasAndTableName;

        public Utils(TableSchemaHelper schemaHelper, 
                    List<String> tablesInQuery, 
                    Dictionary<String, String> entityNameAndLeftJoinedEntityName)
        {
            _schemaHelper = schemaHelper;
            _entityNameAndLeftJoinedEntityName = entityNameAndLeftJoinedEntityName;
            _tablesInQuery = tablesInQuery;
        }

        public Dictionary<String, String> TableAliasAndTableName
        {
            set
            {
                _tableAliasAndTableName = value;
            }
        }

        public String ExtractValFromColumn(Column column,
                                           out KeyValuePair<String, PropertyDetails> matchingClassNameAndTypeInfo,
                                           bool appendValueStringToNullableFields = false)
        {
            int indexOfColumnNameInIdentifier;
            String columnValue;
            String tableName;
            String typeNameForTable;
            Dictionary<String, PropertyDetails> classesContainingColumn;

            //Use TableSchemaHelper class to know the name of class and its data type where input column exists.
            indexOfColumnNameInIdentifier = column.Identifiers.Count == 2 ? 1 : 0;
            classesContainingColumn = _schemaHelper.GetClassNameAndTypeInfo(column.Identifiers[indexOfColumnNameInIdentifier].Value);

            //Example - Orders.CustomerId 
            if (column.Identifiers.Count == 2)  //If length is 2 implies we need not infer the table name.
            {
                typeNameForTable = _schemaHelper.GetClassNameForTableName(_tableAliasAndTableName.ContainsKey(column.Identifiers[0].Value) == true ?
                    _tableAliasAndTableName[column.Identifiers[0].Value] : column.Identifiers[0].Value);
                matchingClassNameAndTypeInfo = GetFirstMatchingTableName(_tablesInQuery, classesContainingColumn, typeNameForTable);

                columnValue = String.Concat("each", GetModifiedTableName(typeNameForTable), ".", matchingClassNameAndTypeInfo.Value.Name);
                columnValue = appendValueStringToNullableFields == true && IsNullable(matchingClassNameAndTypeInfo.Value.TypeInfo) == true ?
                              String.Concat(columnValue, ".Value") : columnValue;
                return columnValue;
            }

            matchingClassNameAndTypeInfo = GetFirstMatchingTableName(_tablesInQuery, classesContainingColumn, null);
            if (classesContainingColumn == null || classesContainingColumn.Count == 0)
            {
                return column.Identifiers[0].Value;
            }

            //Invoke this method to make sure that if table name is changed because of left join..then we use new table name.
            tableName = GetModifiedTableName(matchingClassNameAndTypeInfo.Key);
            columnValue = String.Concat("each", tableName, ".", matchingClassNameAndTypeInfo.Value.Name);
            columnValue = appendValueStringToNullableFields == true && IsNullable(matchingClassNameAndTypeInfo.Value.TypeInfo) == true ?
                          String.Concat(columnValue, ".Value") : columnValue;
            return columnValue;
        }

        public object ExtractValueFromExpression(Expression expression,
                                                 out KeyValuePair<String, PropertyDetails> matchingClassNameAndTypeInfo,
                                                 Type typeForLiteralExp = null,
                                                 bool appendValueStringToNullableFields = false)
        {
            Object columnValue = null;

            //Assign default value.
            matchingClassNameAndTypeInfo = new KeyValuePair<string, PropertyDetails>(String.Empty, null);
            if (expression is Column)
            {
                columnValue = ExtractValFromColumn(expression as Column, out matchingClassNameAndTypeInfo, appendValueStringToNullableFields);
            }
            else if (expression is Literal)
            {
                columnValue = ExtractValFromLiteral(expression as Literal, typeForLiteralExp);
            }
            return columnValue;
        }

        public bool IsExpressionCondition(BinaryExpression expressionToTest)
        {
            return expressionToTest.FirstExpression is Column || expressionToTest.SecondExpression is Column;
        }

        public Object ExtractValFromLiteral(Literal literal, Type dotNetTypeForLiteralValue)
        {
            if (dotNetTypeForLiteralValue == typeof(bool))
            {
                return literal.Value.ToString(CultureInfo.InvariantCulture) == "1" ? bool.TrueString.ToLowerInvariant() : bool.FalseString.ToString(CultureInfo.InvariantCulture);
            }
            if (dotNetTypeForLiteralValue == typeof(string))
            {
                return "\"" + literal.Value.ToString() + "\"";
            }
            if (dotNetTypeForLiteralValue == typeof(DateTime) || dotNetTypeForLiteralValue == typeof(System.Nullable<DateTime>))
            {
                return "DateTime.Parse(\"" + literal.Value.ToString() + "\")";
            }
            switch (literal.LiteralType)
            {
                case LiteralType.AsciiStringLiteral:
                    return "\"" + literal.Value + "\"";
                default:
                    return literal.Value;
            }
        }

        public void EnsureLhsExpIsColumn(Expression firstExp, Expression secondExp, out Expression lhsExp, out Expression rhsExp)
        {
            if (firstExp is Column)
            {
                lhsExp = firstExp;
                rhsExp = secondExp;
                return;
            }
            lhsExp = secondExp;
            rhsExp = firstExp;
        }

        private KeyValuePair<String, PropertyDetails> GetFirstMatchingTableName(List<String> tablesInQuery,
            Dictionary<String, PropertyDetails> classNameAndTypeInfo,
            String tableNameInQuery)
        {
            foreach (String eachTableInQuery in tablesInQuery)
            {
                foreach (KeyValuePair<String, PropertyDetails> eachClassNameAndTypeInfo in classNameAndTypeInfo)
                {
                    if (eachClassNameAndTypeInfo.Key == eachTableInQuery)
                    {
                        if (String.IsNullOrEmpty(tableNameInQuery) == false && tableNameInQuery != eachClassNameAndTypeInfo.Key)
                        {
                            continue;
                        }
                        return eachClassNameAndTypeInfo;
                    }
                }
            }
            return new KeyValuePair<string, PropertyDetails>(String.Empty, null);
        }

        private String GetModifiedTableName(String tableName)
        {
            if (_entityNameAndLeftJoinedEntityName.ContainsKey(tableName) == true)
            {
                return _entityNameAndLeftJoinedEntityName[tableName];
            }
            return tableName;
        }

        private bool IsNullable(Type type)
        {
            return Nullable.GetUnderlyingType(type) == null ? false : true;
        }
    }
}
